import datetime
from scapy.all import Raw, Ether
import json
import base64
import dpkt
from urllib.parse import urlparse, parse_qsl
from scapy_http.http import HTTPRequest, HTTPResponse


class HarObject:
    pass


class NonNegative(object):
    """A descriptor that forbids negative values"""

    def __init__(self, default=None):
        self.default = default
        self.data = {}

    def __get__(self, instance, owner):
        # we get here when someone calls x.d, and d is a NonNegative instance
        # instance = x
        # owner = type(x)
        return self.data.get(instance, self.default)

    def __set__(self, instance, value):
        # we get here when someone calls x.d = val, and d is a NonNegative instance
        # instance = x
        # value = val
        # if value < 0:
        #     raise ValueError("Negative value not allowed: %s" % value)
        self.data[instance] = value


class Creator(HarObject):
    # name = NonNegative()
    # version = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class Browser(HarObject):
    # name = NonNegative()
    # version = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class Page(HarObject):
    # startedDateTime = NonNegative()
    # id = NonNegative()
    # title = NonNegative('Default Page')
    # pageTimings = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class pageTimings(HarObject):
    # onContentLoad = NonNegative(-1)
    # onLoad = NonNegative(-1)
    # comment = NonNegative(-1)

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class Entrie(HarObject):
    # pageref = NonNegative()
    # startedDateTime = NonNegative()
    # time = NonNegative()
    # request = NonNegative()
    # response = NonNegative()
    # cache = NonNegative()
    # timings = NonNegative()
    # serverIPAddress = NonNegative()
    # connection = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class Request(HarObject):
    # method = NonNegative()
    # url = NonNegative()
    # httpVersion = NonNegative()
    # cookies = NonNegative()
    # headers = NonNegative()
    # queryString = NonNegative()
    # postData = NonNegative()
    # headersSize = NonNegative()
    # bodySize = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class Response(HarObject):
    # status = NonNegative()
    # statusText = NonNegative()
    # httpVersion = NonNegative()
    # cookies = NonNegative()
    # headers = NonNegative()
    # content = NonNegative()
    # redirectURL = NonNegative()
    # headersSize = NonNegative()
    # bodySize = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class Cookie(HarObject):
    # name = NonNegative()
    # value = NonNegative()
    # path = NonNegative()
    # domain = NonNegative()
    # expires = NonNegative()
    # httpOnly = NonNegative()
    # secure = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class Header(HarObject):
    # name = NonNegative()
    # value = NonNegative()
    # comment = NonNegative()
    headers = []

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            self.headers.append({'name': key, 'value': value})


class QueryString(HarObject):
    # name = NonNegative()
    # value = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class PostData(HarObject):
    # mimeType = NonNegative()
    # params = NonNegative()
    # text = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class Params(HarObject):
    # name = NonNegative()
    # value = NonNegative()
    # fileName = NonNegative()
    # contentType = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class Content(HarObject):
    # size = NonNegative()
    # compression = NonNegative()
    # mimeType = NonNegative()
    # text = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class Cache(HarObject):
    # beforeRequest = NonNegative()
    # afterRequest = NonNegative()
    # comment = NonNegative()

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class Timings(HarObject):
    blocked = -1
    dns = -1
    connect = -1
    send = -1
    wait = -1
    receive = -1
    ssl = -1
    comment = -1

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


class ResolvePacket:

    def __init__(self):
        self.version = '1.2'
        self.creator = Creator(name='api2har', version='1.2')
        self.browser = Browser(name='api2har', version='1.2')
        self.pages = []
        self.entries = []
        self.comment = ''
        self._dict = {}
        self._request = None
        self._response = None
        self._timing = pageTimings(onContentLoad=-1, onLoad=-1)
        self._load_time = 0
        self._server_ip = None
        self._connection_id = None

    def resolve_packet(self, data: dict):
        self.resolve_request(data.get("request"))
        self.resolve_response(data.get("response"))

    def resolve_page(self):
        self.pages.append(
            Page(startedDateTime='', id='page0', title='test page', pageTimings=self._timing, comment='page'))

    def resolve_entrie(self):
        timings = Timings()
        timings.send = 1
        timings.wait = 1
        timings.receive = 1
        self.entries.append(
            Entrie(pageref='page0', startedDateTime=self._startedDateTime, time=self._load_time, request=self._request,
                   response=self._response, timings=timings, serverIPAddress=self._server_ip,
                   connection=self._connection_id))

    def resolve_request(self, request: HTTPRequest):
        r_raw = b''
        if not request:
            raise ValueError
        self._server_ip = request.Host.decode()
        self._connection_id = -1
        r_raw += request.original
        request_time = request.time
        self._startedDateTime = datetime.datetime.utcfromtimestamp(request_time).isoformat()
        raw = dpkt.http.Request(r_raw)
        headers = Header(**raw.headers).headers
        queryString = []
        for q in parse_qsl(urlparse(raw.uri).query):
            queryString.append(QueryString(name=q[0], value=q[1]))
        postData = None
        if raw.method in ('POST', 'PUT', 'DELETE'):
            postData = PostData(mimeType=raw.headers.get('content-type'), text=raw.body)
        self._request = Request(method=raw.method, url=raw.uri, httpVersion=raw.version, cookies=None,
                                headers=headers,
                                postData=postData, headersSize=len(json.dumps(raw.headers)), bodySize=len(raw.body),
                                _start_time=request_time)

    def resolve_response(self, response: HTTPResponse):
        raw = dpkt.http.Response(response.original)
        headers = Header(**raw.headers).headers
        status = int(raw.status)
        statusText = raw.reason
        httpVersion = raw.version
        cookies = None
        try:
            body = raw.body.decode()
        except UnicodeDecodeError:
            body = raw.body
        content = Content(size=int(getattr(response, 'Content-Length').decode()) if getattr(response, 'Content-Length') is not None else None,
                          mineType=getattr(response, 'Content-Type'),
                          text=body)
        redirectURL = None
        headersSize = len(response.Headers)
        bodySize = len(raw.body)
        self._response = Response(status=status, statusText=statusText, httpVersion=httpVersion,
                                  cookies=cookies,
                                  content=content, redirectURL=redirectURL, headersSize=headersSize,
                                  bodySize=bodySize,
                                  headers=headers)

    def har2dict(self, obj=None):
        _dict = {}
        if not obj:
            obj = self
        if isinstance(obj, dict):
            return obj
        for key, value in obj.__dict__.items():
            if key.startswith('_') or not value:
                continue

            if isinstance(value, HarObject):
                _dict[key] = self.har2dict(obj=value)
            elif isinstance(value, list):
                for v in value:
                    if key not in _dict:
                        _dict[key] = []
                    _dict[key].append(self.har2dict(v))
            elif isinstance(value, bytes):
                try:
                    _dict[key] = value.decode()
                except UnicodeDecodeError:
                    _dict[key] = base64.b64encode(value).decode()
            else:
                _dict[key] = value
        return _dict

    def show(self):
        self.resolve_page()
        self.resolve_entrie()
        self._dict = {"log": self.har2dict()}
        # print(self._dict)


if __name__ == '__main__':
    ResolvePacket().har2dict()
